单例模式
单例模式是指,某个对象在运行时仅存在一个,并对外提供统一访问方式。
- 饿汉模式:类被加载的时候就立即初始化并创建唯一实例
- 懒汉模式:在被首次调用的时候才创建唯一实例。加入双重检查锁机制能保证线程安全。
饿汉单例模式
实现
1 | /** |
测试
1 | public class SingletonTest { |
结果
保证获取到的是唯一实例

懒汉单例模式
实现
1 | /** |
测试
1 | public class SingletonTest { |
结果
懒汉模式同样保证了实例在运行时唯一

单例的安全性
尽管单例模式已经将构造方法设置为 private 但是依然存在让单例不唯一的手段。
- 反射攻击
- 序列化攻击
反射攻击
实现:通过反射创建实例
1 | import java.lang.reflect.Constructor; |
结果:出现了地址不相同的实例,表明通过反射可以破坏单例模式的唯一性

序列化攻击
懒汉式单,实现序列化接口:
1 | import java.io.Serializable; |
测试序列化攻击
1 | import java.io.*; |
结果:在懒汉式单例下,通过序列化来获取单例,可以看到地址不一致。

最佳实践
抵御反射攻击
使用枚举类型
把懒汉式单例存放到枚举类型中,可防止反射和序列化攻击
以懒汉式单例为例子:
实现(枚举):
1 | /** |
测试
1 | package com.coding.singleton; |
结果
可以看到放在枚举中的实:
- 反射的方式读取都是同一个类。
- 序列化方式读取,却不是同一个类,也就是说枚举类型能够抵御反射攻击,却不能抵御序列化攻击

抵御序列化攻击
在类中定义 readResolve 的逻辑
1 | import java.io.Serializable; |
使用同样的测试用例进行测试,结果如下

结论
- 枚举类型实际上是
static的代码块,会在类加载的时候执行,不能被反射创建。枚举类型中的实例是线程安全的。 - 在序列化对象的时候,如果要保证单例,则需要实现
readResolve()方法保证对象唯一。